<?php
class Pageley_Gateway_Database_BasicTable implements Pageley_Gateway_Database_Table {
    private $type;
    private $gateway;
    private $tableName;
    private $schema;
    private $findSql;
    private $insertSql;
    private $updateSql;
    private $deleteSql;
    private $maxIdSql;
    private $resultSetExtractor;
    private $columnNameGenerators;

    public function __construct($type, $gateway) {
        $this->type = $type;
        $this->gateway = $gateway;
        $this->columnNameGenerators = new Pageley_Util_ArrayMap();
        $this->resultSetExtractor = new Pageley_Gateway_Database_EntityResultSetExtractor($type, $this->getTableName());
    }

    public function find($id) {
        $sql = $this->findSql;
        if($sql === null) {
            $alias = $this->defaultAlias();
            $columnNameGenerator = $this->getColumnNameGenerator();
            $sql = "SELECT " . $columnNameGenerator->getColumnNames() .
    			" FROM " . $this->getFullTableName() . " AS " . $alias .
    			" WHERE " . $alias . ".\"" . $this->getTableName() . "_id\" = :" . $this->getTableName() . "_id";
            $this->findSql = $sql;
        }
        $result = null;
        $results = $this->execute($sql, new Pageley_Util_ArrayMap(array($this->getTableName() . "_id" => $id)), $this->resultSetExtractor);
        if($results->size() === 1) {
        	$result = $results->get(0);
        } else if($results->size() > 1) {
        	throw new Pageley_Gateway_Database_Exception("Find by ID on table " . $this->getFullTableName() . " returned more than one record.");
        }
        return $result;
    }

	public function query($query) {
        return $this->execute($query->toSql(), $query->getValues(), $this->resultSetExtractor);
    }

	public function insert($entity) {
        $sql = $this->insertSql;
        if($sql === null) {
            $simpleColumnNameGenerator = $this->getColumnNameGenerator(null, true);
            $insertColumnNameGenerator = $this->getInsertColumnNameGenerator();
            $sql = "INSERT INTO " . $this->getFullTableName() . " ( " . $simpleColumnNameGenerator->getColumnNames() .
                " ) VALUES ( " . $insertColumnNameGenerator->getColumnNames() . " )";
            $this->insertSql = $sql;
        }
        $entity->setId($this->gateway->generateId($this));

        $this->execute($sql, $this->sqlValues($entity));
    }

	public function update($entity) {
        $sql = $this->updateSql;
        if($sql === null) {
            $columnNameGenerator = $this->getUpdateColumnNameGenerator();
            $sql = "UPDATE " . $this->getFullTableName() . " SET " . $columnNameGenerator->getColumnNames() .
                " WHERE \"" . $this->getTableName() . "_id\" = :" . $this->getTableName() . "_id";
            $this->updateSql = $sql;
        }
        $this->execute($sql, $this->sqlValues($entity));
    }

    private function sqlValues($entity) {
        $serializer = Pageley_Serializer_Format::$SQL->getSerializer();
        $values = $serializer->serialize($entity);
        $values->put($this->getTableName() . "_id", $entity->getId());
        return $values;
    }

	public function delete($entity) {
        $sql = $this->deleteSql;
        if($sql === null) {
            $sql = "DELETE FROM " . $this->getFullTableName() .
                " WHERE \"" . $this->getTableName() . "_id\" = :" . $this->getTableName() . "_id";
            $this->deleteSql = $sql;
        }
        $this->execute($sql, new Pageley_Util_ArrayMap(array(
                $this->getTableName() . "_id" => $entity->getId())));
    }

    private function execute($sql, $values, $resultSetExtractor = null) {
        $transaction = $this->gateway->getTransaction(true, true);
        $connection = $transaction->getConnection();
        $statement = $connection->prepareStatement($sql);
        $results = null;
        try {
        	$resultSet = $statement->execute($values);
        	if($resultSetExtractor !== null) {
        	    $results = $resultSetExtractor->extractResults($resultSet);
        	}
        } catch(Exception $e1) {
        	try {
                $transaction->rollback();
        	} catch(Exception $e2) {
        	    Pageley_Log::error($this, "Failed to roll back: " . $e2);
        	}
        	throw $e1;
        }
        if($transaction->getSingleStatement()) {
        	$transaction->commit();
        }
        return $results;
    }
    
    private function execute0($sql, $values) {
        
    }

    private function extractResults($resultSet) {
        $results = new Pageley_Util_ArrayMap();
        $serializer = Pageley_Serializer_Format::$SQL->getSerializer();
        while($resultSet->hasNext()) {
            $resultSet->next();
            $entity = $serializer->deserialize($resultSet, $this->type);
            $entity->setId($resultSet->get($this->getTableName() . "_id"));
            $results->add($entity);
        }
        return $results;
    }

    private function defaultAlias() {
        return Pageley_Util_String::toLowercase(Pageley_Util_String::substring(
                $this->type->getName(), 0, 1)) . "1";
    }

	public function getTableName() {
        $tableName = $this->tableName;
        if($tableName === null) {
    		$tableName = Pageley_Util_String::addUnderscores($this->type->getName());
            $this->tableName = $tableName;
        }
		return $tableName;
	}

	public function getSchema() {
		$schema = $this->schema;
        if($schema === null) {
    		$schema = Pageley_Util_String::toLowerCase($this->type->getNamespace()->getSchema());
            $this->schema = $schema;
        }
		return $schema;
	}

	public function getFullTableName() {
		return "\"" . $this->getSchema() . "\".\"" .$this->getTableName() . "\"";
	}

	public function getMaxId() {
		$sql = $this->maxIdSql;
        if($sql === null) {
            $alias = $this->defaultAlias();
            $sql = "SELECT MAX("  . $alias . ".\"" . $this->getTableName() . "_id\") AS max_id" .
    			" FROM " . $this->getFullTableName() . " AS " . $alias;
        }
        return $this->execute($sql, null, new Pageley_Gateway_Database_MaxIdResultSetExtractor());
	}
    
    public function count($query) {
        return $this->execute($query->toSql(Pageley_Gateway_Database_QueryType::$COUNT), $query->getValues(), new Pageley_Gateway_Database_CountResultSetExtractor());
    }
    
    public function __toString() {
        return "table " . $this->getFullTableName();
    }
    
    public function getColumnNameGenerator($aliasPrefix = null, $omitTableAlias = false) {
        $key = ($aliasPrefix !== null ? "A'" . $aliasPrefix . "'" : "") . "/O" . $omitTableAlias;
        $columnNameGenerator = $this->columnNameGenerators->get($key);
        if($columnNameGenerator === null) {
            $tableAlias = $omitTableAlias ? null : $this->defaultAlias();
            if($aliasPrefix !== null) {
                $columnNameGenerator = new Pageley_Gateway_Database_AliasColumnNameGenerator(
                        $this->type, $this->getTableName(), $tableAlias, "\"", $aliasPrefix);
            } else {
                $columnNameGenerator = new Pageley_Gateway_Database_SimpleColumnNameGenerator(
                        $this->type, $this->getTableName(), $tableAlias, "\"");
            }
            $this->columnNameGenerators->put($key, $columnNameGenerator);
        }
        return $columnNameGenerator;
    }
    
    private function getUpdateColumnNameGenerator() {
        $columnNameGenerator = $this->columnNameGenerators->get("UPDATE");
        if($columnNameGenerator === null) { 
            $columnNameGenerator = new Pageley_Gateway_Database_UpdateColumnNameGenerator(
                $this->type, $this->getTableName(), "\"");
            $this->columnNameGenerators->put("UPDATE", $columnNameGenerator);
        }
        return $columnNameGenerator;
    }
    
    private function getInsertColumnNameGenerator() {
        $columnNameGenerator = $this->columnNameGenerators->get("INSERT");
        if($columnNameGenerator === null) { 
            $columnNameGenerator = new Pageley_Gateway_Database_InsertColumnNameGenerator(
                $this->type, $this->getTableName());
            $this->columnNameGenerators->put("INSERT", $columnNameGenerator);
        }
        return $columnNameGenerator;
    }

}